#include "serial.h"

#define UART2_CATCH()	{FIO4DIR |= 0x80000000; FIO4SET =  0x80000000;}
#define UART2_RELEASE()	{FIO4DIR |= 0x80000000; FIO4CLR =  0x80000000;}
#define UART2_SET_RTS()	{FIO0DIR |= 0x00001000; FIO0SET =  0x00001000;}
#define UART2_CLR_RTS()	{FIO0DIR |= 0x00001000; FIO0CLR =  0x00001000;}
#define UART2_SET_DTR()	{FIO0DIR |= 0x00004000; FIO0CLR =  0x00004000;}
#define UART2_CLR_DTR()	{FIO0DIR |= 0x00004000; FIO0CLR =  0x00004000;}
#define UART2_CTS()	(FIO0PIN &  0x00002000)
#define UART2_DSR()	(FIO0PIN &  0x00008000)
#define UART2_DCD()	(FIO0PIN &  0x00010000)
#define UART2_RI()	(FIO0PIN &  0x00020000)


/* In lpc2378ctl.c */
extern unsigned long get_uart_clk(unsigned long n, unsigned long fosc);

static buffer_t  cbuf[2];
static char      data[2][SERIAL_BUFFER_SIZE];



/* Data received handler */
static void UART0_ISR(void)
{
  volatile unsigned char reg, lsr;

  while (((reg = U0IIR) & 0x01) == 0){
    switch (reg & ((1<<IIR1) | (1<<IIR2) | (1<<IIR3))){
      case (1<<IIR1) | (1<<IIR2): // Received line status
        lsr = U0LSR;
        if (lsr & ((U0LSR_OE_MASK|U0LSR_PE_MASK|U0LSR_FE_MASK|U0LSR_RXFE_MASK|U0LSR_BI_MASK))){
          U0RBR;
        }
        else if (lsr & U0LSR_RDR_MASK) {
          while (U0LSR & (1<<U0LSR_RDR_BIT)) {
            char c = U0RBR;
            buffer_put(&cbuf[0], c);
          }
          
        }
      break;
      case (1<<IIR2):             // Receive Data Available
      case (1<<IIR2) | (1<<IIR3): // Character Timeout Indicator
        while (U0LSR & (1<<U0LSR_RDR_BIT)){
          char c = U0RBR;
          buffer_put(&cbuf[0], c);
        }
      break;
      default:                    // Other
        U0LSR;
        U0RBR;
    }
  }
}

/* Data received handler */
static void UART2_ISR(void)
{
  volatile unsigned char reg, lsr;

  while (((reg = U2IIR) & 0x01) == 0){
    switch (reg & ((1<<IIR1) | (1<<IIR2) | (1<<IIR3))){
      case (1<<IIR1) | (1<<IIR2): // Received line status
        lsr = U2LSR;
        if (lsr & ((U2LSR_OE_MASK|U2LSR_PE_MASK|U2LSR_FE_MASK|U2LSR_RXFE_MASK|U2LSR_BI_MASK))){
          U2RBR;
        }
        else if (lsr & U2LSR_RDR_MASK) {
          while (U2LSR & (1<<U2LSR_RDR_BIT)) {
            buffer_put(&cbuf[1], U2RBR);
          }
        }
      break;
      case (1<<IIR2):             // Receive Data Available
      case (1<<IIR2) | (1<<IIR3): // Character Timeout Indicator
        while (U2LSR & (1<<U2LSR_RDR_BIT)){
          buffer_put(&cbuf[1], U2RBR);
        }
      break;
      default:                    // Other
        U2LSR;
        U2RBR;
    }
  }
}

void uart0_enable(int baud)
{
  /* Turn UART0 on */
  //PCONP |= (1<<PCONP_PCUART0_BIT);

   /* Initialize Pin Select (PINSEL0) for UART0 */
  PINSEL0 &= ~((1<<5) + (1<<7));
  PINSEL0 |=  ((1<<4) + (1<<6));

  /* Turn UART0 on */
  FIO0DIR |= (1<<8); //P0.8
  FIO0CLR =  (1<<8); //P0.8

  /* A small delay */
  delay_ms(100);

  /* Set UART1 data mode and set divisor latch. 
  (* 083H - 8 bits, no parity, 1 stop bit *)
  (* 087H - 8 bits, no parity, 2 stop bits */
  U0LCR = (1<<U0LCR_Divisor_Latch_Access_Bit_BIT) + 0x03;
  
  if (baud == 0){
    /* Enable autobaud */ 
    U0ACR |= ((1<<U0ACR_Start_BIT) + (1<<U0ACR_Mode_BIT));
  }
  else {
    /* Set baudrate */
    unsigned int divisor = get_uart_clk(0, OSCILLATOR_CLOCK_FREQUENCY) / (16 * baud);
    U0DLL = divisor & 0xFF;
    U0DLM = (divisor >> 8) & 0xFF; 
  }

  /* Clear Divisor Latch */
  U0LCR &= ~(1<<U0LCR_Divisor_Latch_Access_Bit_BIT);
  U0FCR = 1;
               
  /* Configure the Vectored Interrupt Controller */
  /* Install UART0 handler */
  ctl_set_isr(VICIntSelect_UART0_BIT, 0x03, CTL_ISR_TRIGGER_FIXED, UART0_ISR, 0);
  /* Enable UART0 interrupt */
  ctl_unmask_isr(VICIntSelect_UART0_BIT);

  /* Enable data receive interrupt */
  U0IER = (1<<U0IER_RBR_Interrupt_Enable_BIT);   
}

void uart2_enable(int baud)
{
  /* Turn UART0 on */
  PCONP |= (1<<PCONP_PCUART2_BIT);

   /* Initialize Pin Select (PINSEL0) for UART2 */
  PINSEL0 = (PINSEL0 & ~0x00300000) | 0x00100000;
  PINSEL0 = (PINSEL0 & ~0x00C00000) | 0x00400000;
  
  /* Turn UART2 on */
  FIO0DIR |= (1<<9);//P0.9 
  FIO0CLR =  (1<<9);//P0.9

  /* A small delay */
  delay_ms(100);

  /* Set UART1 data mode and set divisor latch. 
  (* 083H - 8 bits, no parity, 1 stop bit *)
  (* 087H - 8 bits, no parity, 2 stop bits */
  U2LCR = (1<<U2LCR_Divisor_Latch_Access_Bit_BIT) + 0x03;
  
  if (baud == 0){
    /* Enable autobaud */ 
    U2ACR |= ((1<<U2ACR_Start_BIT) + (1<<U2ACR_Mode_BIT));
  }
  else {
    /* Set baudrate */
    unsigned int divisor = get_uart_clk(2, OSCILLATOR_CLOCK_FREQUENCY) / (16 * baud);
    U2DLL = divisor & 0xFF;
    U2DLM = (divisor >> 8) & 0xFF; 
  }

  /* Clear Divisor Latch */
  U2LCR &= ~(1<<U2LCR_Divisor_Latch_Access_Bit_BIT);
  U2FCR = 1;
               
  /* Configure the Vectored Interrupt Controller */
  /* Install UART0 handler */
  ctl_set_isr(VICIntSelect_UART2_BIT, 0x03, CTL_ISR_TRIGGER_FIXED, UART2_ISR, 0);
  /* Enable UART0 interrupt */
  ctl_unmask_isr(VICIntSelect_UART2_BIT);

  /* Enable data receive interrupt */
  U2IER = (1<<U2IER_RBR_Interrupt_Enable_BIT);   
}

void serial_enable(SerialPort_t port, int baud)
{
  if (port == COM3) return; //TODO:Add COM3(RS485) support
  cbuf[port] = buffer_create(data[port], SERIAL_BUFFER_SIZE); 
  if (port == COM0) uart0_enable(baud);
  if (port == COM2) uart2_enable(baud);
}

int  serial_available(SerialPort_t port)
{
  return cbuf[port].available;
}

int serial_read(SerialPort_t port, char* dest, int length)
{
  int available = serial_available(port);
  int min = (length<available)?(length):(available);
  for (int i = 0; i < min; i++)
  {
    dest[i] = buffer_get(&cbuf[port]);
  }
  return min;
}

/* Send a single character using polling */
void serial_putc(SerialPort_t port, unsigned char c)
{
  unsigned int i = 0;
  volatile unsigned char* lsr = NULL;
  volatile unsigned char* thr = NULL;

  if (port == COM0) lsr = &U0LSR, thr = &U0THR;
  else if (port == COM2) lsr = &U2LSR, thr = &U2THR;
  else return;
  
  while (!((*lsr) & (1<<5)))
    if (i++ > 0x1000) break;
  *thr = c;
}

void serial_puts(SerialPort_t port, unsigned char *str)
{
  while(*str) serial_putc(port, *str++);
}

void serial_putn(SerialPort_t port, unsigned char *buffer, int length)
{
  for(int i = 0; i < length; i++) {
    serial_putc(port, buffer[i]);
  }
}

void serial_flush(SerialPort_t port)
{
  buffer_flush(&cbuf[port]);
}

/* Catch data in transparent mode */
void serial_catch(SerialPort_t port)
{
  if (port == COM2) UART2_CATCH();
}

/* Release data in transparent mode */
void serial_release(SerialPort_t port)
{
  if (port == COM2) UART2_RELEASE();
}

void serial_close(SerialPort_t port)
{
  if (port == COM0) {
    /* Turn UART0 off */
    FIO0DIR |= (1<<PINSEL0_P0_8_BIT); 
    FIO0SET  = (1<<PINSEL0_P0_8_BIT);
  }
  if (port == COM2) {
    /* Turn UART2 off */
    FIO0DIR |= (1<<PINSEL0_P0_9_BIT); 
    FIO0SET  = (1<<PINSEL0_P0_9_BIT);
  }
}
